// Copyright (c) Microsoft Corporation. All rights reserved.

namespace Microsoft.Tools.CodeRunner.UnitTest
{
	using System;
	using System.Text;
	using System.Collections.Generic;
	using Microsoft.VisualStudio.TestTools.UnitTesting;
	using System.IO;
	using System.Xml;
	using System.Text.RegularExpressions;

	[TestClass]
	public class CsrTests 
	{
		private static TestContext context;

		public CsrTests() 
		{
		}

		[ClassInitialize()]
		public static void ClassInitialize(TestContext context)
		{
			CsrTests.context = context;
		}
		
		[TestInitialize()]
		public void TestInitialize()
		{
			// Make .config file writeable
			File.SetAttributes("csr.exe.config", File.GetAttributes("csr.exe.config") & ~FileAttributes.ReadOnly);
			File.SetAttributes("csc.rsp", File.GetAttributes("csc.rsp") & ~FileAttributes.ReadOnly);

			// Save original csc.rsp
			File.Copy("csc.rsp", "csc.save.rsp", true);
		}

		[TestMethod]
		public void TestEmptyCommandLine()
		{
			string output;

			int n = Command.Run("csr.exe", out output);

			Assert.IsTrue(Regex.IsMatch(output, @"\r\nERROR: .*\r\n", RegexOptions.Multiline));
		}

		[TestMethod]
		public void TestNoConfigFile()
		{
			string output;

			try
			{
				File.Move("csr.exe.config", "__csr.exe.config");

				int n = Command.Run("csr.exe", out output);

				Assert.IsTrue(Regex.IsMatch(output, @"\r\nERROR: .*\r\n", RegexOptions.Multiline));
			}
			finally
			{
				if (File.Exists("__csr.exe.config"))
					File.Move("__csr.exe.config", "csr.exe.config");
			}
		}

		[TestMethod]
		public void TestBogusSwitch()
		{
			string output;
		
			Command.Run("csr.exe /bogus", out output);
            
			Assert.IsTrue(Regex.IsMatch(output, @"\r\nERROR: Unknown or invalid.*\r\n", RegexOptions.Multiline));
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\HelloWorld.csr")]
		public void TestHelloWorldWithArgs()
		{
			string output;
			string cmd = "csr.exe helloworld.csr 1 2 3";

			Command.Run(cmd, out output);

			Assert.IsTrue(Regex.IsMatch(output, @"Hello world!\r\n1\r\n2\r\n3\r\n", RegexOptions.Multiline));
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\GetScriptName.csr")]
		public void TestGetScriptName()
		{
			string output;
			string cmd = "csr.exe getscriptname";  // Left off the .csr on purpose
			
			Command.Run(cmd, out output);
            
			Assert.IsTrue(Regex.IsMatch(output, @".*getscriptname\.csr.*\r\n", RegexOptions.Multiline | RegexOptions.IgnoreCase));
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\Unsafe.csr")]
		public void TestUnsafe()
		{
			// TODO: Make sure that if this test fails, the .config gets put back the way it was

			string output;
			string cmd = "csr.exe unsafe.csr";

			XmlPoke("csr.exe.config", "/configuration/appSettings/add[@key='AllowUnsafeCode']/@value", "no");

			Command.Run(cmd, out output);

			// Save the result so that we can return the .config file to it's old state
			bool succeeded = Regex.IsMatch(output, @"^.*Unsafe\.csr.* error CS0227.*\r\n", RegexOptions.Multiline);

			XmlPoke("csr.exe.config", "/configuration/appSettings/add[@key='AllowUnsafeCode']/@value", "yes");

			Assert.IsTrue(succeeded);

			Command.Run(cmd, out output);

			Assert.IsTrue(Regex.IsMatch(output, @"Success\r\n", RegexOptions.Multiline));
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\HelloWorld.csr")]
		public void TestEnvironmentConfig()
		{
			string output;
			string error;
			string cmd = "csr.exe helloworld.csr";

			Environment.SetEnvironmentVariable("CSR_CONFIG", "DebugMessages=yes");

			Command.Run(cmd, out output, out error);

			Assert.IsTrue(Regex.IsMatch(output, @"Hello world!\r\n", RegexOptions.Multiline));
			Assert.IsTrue(Regex.IsMatch(error, @"DEBUG:.*\r\n", RegexOptions.Multiline));

			// Explicitly turn off debug messages
			Environment.SetEnvironmentVariable("CSR_CONFIG", "DebugMessages=");
			
			Command.Run(cmd, out output, out error);

			Assert.IsTrue(Regex.IsMatch(output, @"Hello world!\r\n", RegexOptions.Multiline));
			Assert.IsTrue(error.Length == 0);
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\FunkyType\1.0\FunkyType.dll")]
		[DeploymentItem(@"UnitTest\FunkyType\2.0\FunkyType.dll", "FunkyType")]
		[DeploymentItem(@"UnitTest\TestSource\UseFunkyType.csr", "FunkyType")]
		public void TestLocalAssemblyOverride()
		{
			string output;
			
			// Put /r:FunkyType.dll in the shared csc.rsp file
			using (StreamWriter wr = File.AppendText("csc.rsp"))
			{
				wr.WriteLine("\n/r:FunkyType.dll");
			}

			// Set the time back on the response file a bit, so it doesn't look newer than any scripts we compile below and prevent the cached images being used.
			File.SetLastAccessTime("csc.rsp", DateTime.Now.Subtract(TimeSpan.FromSeconds(10.0)));

			// This should use the FunkyType.dll in the csr.exe directory.
			Command.Run(@"csr.exe FunkyType\UseFunkyType.csr", out output);
			Assert.AreEqual("1\r\n", output);
			
			// After the last step the script SHOULD be cached.  Let's check.
			string cachedScript = ScriptEnvironment.CachePath + ScriptEnvironment.CachifySourceName(new ParsedPath(@"FunkyType\UseFunkyType.csr").MakeFullPath());
			
			Assert.IsTrue(File.Exists(cachedScript));
			
			// Create a script .rsp and put /r:FunkyType.dll in it
			using (StreamWriter wr = File.AppendText(@"FunkyType\UseFunkyType.rsp"))
			{
				wr.WriteLine("/r:FunkyType.dll");
			}

			// This will FAIL because FunkyType is now defined in two referenced assemblies in the response files.
			// Note, the response file for the script is now newer than the script, which will cause a recompile.
			// TODO: This is annoying, but the fix here would we to use aliased namespaces.  See UseNewFunkyType.csr
			// /r: should accept the aliased reference syntax without modification.
			Command.Run(@"csr.exe FunkyType\UseFunkyType.csr", out output);
			Assert.IsTrue(Regex.Match(output, @"error CS0433").Success);

			// If we set the time back on the script .rsp we would pick up the cached image from the previous run.  But that's an 
			// artificial thing to do.  We will assume that all file write times normally move forward.

			// Delete the shared .rsp so we can test what happens without it.  Delete the cached script to, to ensure a recompile
			// because the cache logic can't check the write time on a file that has been deleted, so it just assumes that the file
			// has not been modified (optimisitic approach).  Otherwise we'd never run anything from the cache.
			File.Delete(@"csc.rsp");
			File.Delete(cachedScript);

			// Will recompile and pick up the FunkyType.dll from the script directory.  Do it twice to make sure cached image runs the same.
			Command.Run(@"csr.exe FunkyType\UseFunkyType.csr", out output);
			Assert.AreEqual("2\r\n", output);
			Command.Run(@"csr.exe FunkyType\UseFunkyType.csr", out output);
			Assert.AreEqual("cached 2\r\n", output);

			// Delete the script .rsp (and any cached image) to see what happens without that
			File.Delete(@"FunkyType\\UseFunkyType.rsp");
			File.Delete(cachedScript);

			// This picks up FunkyType.dll in the script directory, because of the OnAssemblyResolve handler in csr.exe
			Command.Run(@"csr.exe /r:FunkyType.dll FunkyType\UseFunkyType.csr", out output);
			Assert.AreEqual("2\r\n", output);
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\IsCachedScript.csr")]
		public void TestCache()
		{
			string output1;
			string output2;
			string cmd = "csr.exe IsCachedScript.csr";

			XmlPoke("csr.exe.config", "/configuration/appSettings/add[@key='CacheImages']/@value", "no");

			Command.Run(cmd, out output1);

			XmlPoke("csr.exe.config", "/configuration/appSettings/add[@key='CacheImages']/@value", "yes");
			
			// Run twice to let script be cached
			Command.Run(cmd, out output2);
			Command.Run(cmd, out output2);
            
			Assert.AreEqual("no\r\n", output1);
			Assert.AreEqual("yes\r\n", output2);
		}

		[TestMethod]
		public void TestHelpSwitch()
		{
			string output1;

			Command.Run("csr.exe /?", out output1);

			Assert.IsTrue(Regex.IsMatch(output1,
				@"^C# Code Runner\. (Debug|Release) Version \d+\.\d+\.\d+\.\d+\r\nCopyright \(c\) Microsoft Corporation\..*\r\n\r\nUsage: csr .*\r\n\r\nOptions:.*\r\n\r\n(?:(/\w+.*\r\n)|(?:\s+.*\r\n))+(.*\r\n)+",
				RegexOptions.Multiline | RegexOptions.ExplicitCapture));

			string output2;

			Command.Run("csr /HELP", out output2);

			Assert.AreEqual(output1, output2);

			Command.Run("csr /HELP blah", out output2);

			Assert.AreEqual(output1, output2);
		}

		[TestMethod]
		[DeploymentItem(@"UnitTest\TestSource\Throws.csr")]
		public void TestScriptException()
		{
			string output;

			Environment.SetEnvironmentVariable("CSR_CONFIG", "DebugMessages=yes");
				
			// First run is not cached
			Command.Run("csr.exe Throws.csr", out output);
			
			Assert.IsTrue(Regex.IsMatch(output,
				@"(DEBUG:.*\r\n){3,}ERROR:.*Object reference not set.*\r\nDEBUG:.*\r\n.*line 7",
				RegexOptions.Multiline | RegexOptions.ExplicitCapture));
				
			// Do it again to try the cached one
			Command.Run("csr Throws.csr", out output);

			Assert.IsTrue(Regex.IsMatch(output,
				@"(DEBUG:.*\r\n){3,}ERROR:.*Object reference not set.*\r\nDEBUG:.*\r\n.*line 7",
				RegexOptions.Multiline | RegexOptions.ExplicitCapture));
		}

		[TestCleanup()]
		public void TestCleanup()
		{
			if (File.Exists("csc.save.rsp"))
			{
				// Put the csc.rsp file back the way it was
				File.Copy("csc.save.rsp", "csc.rsp", true);
				File.Delete("csc.save.rsp");
			}

			// Remove any environment configuration
			Environment.SetEnvironmentVariable("CSR_CONFIG", null);
		}

		private void XmlPoke(string fileName, string xpath, string nodeValue)
		{
			XmlDocument document = null;

			try 
			{
				document = new XmlDocument();
				document.Load(fileName);

				XmlNodeList nodes = document.SelectNodes(xpath);

				foreach (XmlNode node in nodes)  
				{
					node.InnerXml = nodeValue;
				}

				document.Save(fileName);
			} 
			catch 
			{
				Assert.Fail(String.Format("Unable to replace XML node '{0}' in '{1}'", nodeValue, xpath));
			}
		}
	}
}